﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	    : FI.Net Application Framework
// COMPONENT ID	    : FI.Core
// COMPONENT DESC   : FI.Net Core Foundation
//
// CREATED DATE/BY  : 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using FI.Core.Properties;
using FI.Core.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;

namespace FI.Core
{
    /// <summary>
    /// A collection of stateful entries.
    /// </summary>
    /// <typeparam name="T">The type of element of collection.</typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Stateful"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), CollectionDataContract(Namespace = Constants.Namespace)]
    public class AuditableStatefulSet<T>: IEnumerable<Auditable<T>> where T: StatefulEntry
    {
        [DataMember]
        private Collection<Auditable<T>> auditables = new Collection<Auditable<T>>();

        /// <summary>
        /// Adds the specified auditable entry.
        /// </summary>
        /// <remarks>This method is used for deserialization.</remarks>
        /// <param name="auditableEntry">The auditable entry.</param>
        private void Add(Auditable<T> auditableEntry)
        {
            auditables.Add(auditableEntry);
        }

        /// <summary>
        /// Adds the specified auditable entries.
        /// </summary>
        /// <param name="auditableEntries">The auditable entries.</param>
        public void Add(params Auditable<T>[] auditableEntries)
        {
            Guard.ArgumentNotNullOrEmpty(auditableEntries, "auditableEntries");
            Array.ForEach(auditableEntries, auditable =>
                {
                    if (null != auditable.Entry.Parent)
                    {
                        throw new InvalidOperationException(Resources.ExceptionEntryAddedIntoAnotherSet);
                    }

                    if (auditables.Contains(auditable))
                    {
                        throw new InvalidOperationException(Resources.ExceptionEntryAddedIntoSet);
                    }
                    auditables.Add(auditable);
                    auditable.Entry.Parent = this;
                    auditable.Entry.State = EntryState.Added;
                });
        }

        /// <summary>
        /// Removes the specified auditable entries.
        /// </summary>
        /// <param name="auditableEntries">The auditable entries.</param>
        public void Remove(params Auditable<T>[] auditableEntries)
        {
            Guard.ArgumentNotNullOrEmpty(auditableEntries, "auditableEntries");
            Array.ForEach(auditableEntries, auditable =>
                {
                    if (!auditables.Contains(auditable))
                    {
                        throw new InvalidOperationException(Resources.ExceptionEntryNotAddedIntoSet);
                    }

                    auditables.Remove(auditable);
                    auditable.Entry.State = EntryState.Detached;
                    auditable.Entry.Parent = null;
                });
        }

        /// <summary>
        /// Deletes the specified auditable entries.
        /// </summary>
        /// <param name="auditableEntries">The auditable entries.</param>
        public void Delete(params Auditable<T>[] auditableEntries)
        {
            Guard.ArgumentNotNullOrEmpty(auditableEntries, "auditableEntries");
            Array.ForEach(auditableEntries, auditable =>
                {
                    if (!auditables.Contains(auditable))
                    {
                        throw new InvalidOperationException(Resources.ExceptionEntryNotAddedIntoSet);
                    }

                    if (auditable.Entry.State == EntryState.Added)
                    {
                        this.Remove(auditable);
                    }
                    else
                    {
                        auditable.Entry.State = EntryState.Deleted;
                    }
                });
        }

        /// <summary>
        /// Accepts the changes.
        /// </summary>
        /// <param name="auditableEntries">The auditable entries.</param>
        /// <exception cref="System.InvalidOperationException"></exception>
        public AuditableStatefulSet<T> AcceptChanges(params Auditable<T>[] auditableEntries)
        {
            Auditable<T>[] entries = auditableEntries ?? auditables.ToArray();
            if (entries.Length == 0)
            {
                entries = auditables.ToArray();
            }
            if (!entries.All(auditable => auditables.Contains(auditable)))
            {
                throw new InvalidOperationException(Resources.ExceptionEntryNotAddedIntoSet);
            }
            Array.ForEach(entries, auditable =>
                {
                    if (auditable.Entry.State == EntryState.Deleted)
                    {
                        this.Remove(auditable);
                    }
                    else
                    {
                        auditable.Entry.State = EntryState.Unchanged;
                    }
                });
            return this;
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<Auditable<T>> GetEnumerator()
        {
            return auditables.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return auditables.GetEnumerator();
        }
    }
}
